package com.openvid.webrtc.openvid;

import android.text.TextUtils;
import android.util.Log;

import com.openvid.webrtc.openvid.cmd.AbsRequestCmd;
import com.openvid.webrtc.openvid.cmd.ice_candidate.CmdIceCandidate;
import com.openvid.webrtc.openvid.cmd.join_room.CmdJoinRoom;
import com.openvid.webrtc.openvid.cmd.join_room.RespJoinRoom;
import com.openvid.webrtc.openvid.cmd.leave_room.CmdLeaveRoom;
import com.openvid.webrtc.openvid.cmd.publish_video.CmdPublishVideo;
import com.openvid.webrtc.openvid.obs.CustomPeerConnectionObserver;
import com.openvid.webrtc.openvid.obs.CustomSdpObserver;
import com.openvid.webrtc.openvid.websocket.IWebSocket;
import com.openvid.webrtc.openvid.websocket.IWebSocketCallback;
import com.openvid.webrtc.openvid.websocket.OpenVidWebSocket;

import org.json.JSONException;
import org.json.JSONObject;
import org.webrtc.IceCandidate;
import org.webrtc.MediaConstraints;
import org.webrtc.PeerConnection;
import org.webrtc.SessionDescription;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author jinyf
 */
public class RtcRoomDelegate {


    private static final String TAG = "WebSocket";
    private AtomicInteger RPC_ID = new AtomicInteger(0);

    private Map<Integer, AbsRequestCmd> mCmds = new HashMap<>();

    private IWebSocket mSocket;
    private Session mSession;
    private IParticipant mLocalParticipant;
    private RtcRoomDelegateCallback mRtcRoomDelegateCallback;

    private int generateRPCId() {
        return RPC_ID.incrementAndGet();
    }

    public RtcRoomDelegate(Session session) {
        mSession = session;
        initLocalParticipan();
        mSocket = new OpenVidWebSocket(new IWebSocketCallback() {
            @Override
            public void onConnected() {
                joinRoom();
            }

            @Override
            public void onTextMessageReceived(String text) {
                try {
                    onResopnse(text);
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    private void initLocalParticipan() {
        mLocalParticipant=new LocalFileParticipan("xiaoyaLocal",mSession);
    }

    private void joinRoom() {
        CmdJoinRoom cmd = new CmdJoinRoom();
        String name = mLocalParticipant.getParticipantName();
        cmd.setParams(name, mSession.getId(), mSession.getToken());
        cmd.setCallback(respJoinRoom -> {
            joinRoomResponse(respJoinRoom);
        });
        sendCmd(cmd);
    }

    private void joinRoomResponse(RespJoinRoom respJoinRoom) {
        mLocalParticipant.setConnectionId(respJoinRoom.getId());
//            String mMediaServer = respJoinRoom.getMediaServer();
        List<RespJoinRoom.RespIceServer> cs = respJoinRoom.getCustomIceServers();
        if (cs != null) {
            List<PeerConnection.IceServer> iceServers = new ArrayList();
            List<String> urls = new ArrayList<String>();
            for (RespJoinRoom.RespIceServer is : cs) {
                urls.add(is.getUrl());
                if (is.getUrls() != null) {
                    urls.addAll(new ArrayList<>(Arrays.asList(is.getUrls())));
                }
                PeerConnection.IceServer.Builder iceServerBuilder;
                try {
                    iceServerBuilder = PeerConnection.IceServer.builder(urls);
                } catch (IllegalArgumentException e) {
                    continue;
                }
                if (!TextUtils.isEmpty(is.getUsername())) {
                    iceServerBuilder.setUsername(is.getUsername());
                }
                if (!TextUtils.isEmpty(is.getCredential())) {
                    iceServerBuilder.setPassword(is.getCredential());
                }
                iceServers.add(iceServerBuilder.createIceServer());
            }
            mSession.setIceServers(iceServers);
        }

        String turnHost = respJoinRoom.getCoturnIp();
        String turnPort = respJoinRoom.getCoturnPort();
        if (!TextUtils.isEmpty(turnHost) && !TextUtils.isEmpty(turnPort)) {
            mSession.setIceServerUri("turn:" + turnHost + ":" + turnPort);
        }
        String turnUser = respJoinRoom.getTurnUsername();
        if (!TextUtils.isEmpty(turnUser)) {
            mSession.setIceServerUser(turnUser);
        }
        String turnPass = respJoinRoom.getTurnCredential();
        if (!TextUtils.isEmpty(turnPass)) {
            mSession.setIceServerPass(turnPass);
        }

        PeerConnection localPeerConnection =createLocalPeerConnection(mLocalParticipant);
        mLocalParticipant.setPeerConnection(localPeerConnection);
        MediaConstraints sdpConstraints = new MediaConstraints();
        sdpConstraints.mandatory.add(new MediaConstraints.KeyValuePair("offerToReceiveAudio", "false"));
        sdpConstraints.mandatory.add(new MediaConstraints.KeyValuePair("offerToReceiveVideo", "false"));
        mLocalParticipant.getPeerConnection().createOffer(new CustomSdpObserver("createOffer") {
            @Override
            public void onCreateSuccess(SessionDescription sdp) {
                super.onCreateSuccess(sdp);
                Log.i("createOffer SUCCESS", sdp.toString());
                mLocalParticipant.getPeerConnection().setLocalDescription(new CustomSdpObserver("createOffer_setLocalDescription") {
                    @Override
                    public void onSetSuccess() {
                        super.onSetSuccess();
                        publishVideo(sdp);
                    }
                }, sdp);
            }
        }, sdpConstraints);
//                if (result.getJSONArray(JsonConstants.VALUE).length() > 0) {
//                    // There were users already connected to the session
//                addRemoteParticipantsAlreadyInRoom(result);
//                }
    }

    private PeerConnection createLocalPeerConnection(IParticipant localParticipant) {
        return mSession.createLocalPeerConnection(localParticipant, new CustomPeerConnectionObserver("local") {
            @Override
            public void onIceCandidate(IceCandidate ic) {
                super.onIceCandidate(ic);
                CmdIceCandidate cmd=new CmdIceCandidate();
                cmd.setParams(localParticipant.getConnectionId(),ic.sdp,ic.sdpMid,ic.sdpMLineIndex);
                sendCmd(cmd);
            }

            @Override
            public void onSignalingChange(PeerConnection.SignalingState signalingState) {
                if (PeerConnection.SignalingState.STABLE.equals(signalingState)) {
                    Iterator<IceCandidate> it = localParticipant.getIceCandidateList().iterator();
                    while (it.hasNext()) {
                        IceCandidate candidate = it.next();
                        localParticipant.getPeerConnection().addIceCandidate(candidate);
                        it.remove();
                    }
                }
            }
        });
    }

    private void publishVideo(SessionDescription sessionDescription) {
        CmdPublishVideo cmd = new CmdPublishVideo();
        cmd.setParams(sessionDescription.description);
        cmd.setCallback(resp -> {
            SessionDescription remoteSdpAnswer = new SessionDescription(
                    SessionDescription.Type.ANSWER, resp.getSdpAnswer());
            mLocalParticipant
                    .getPeerConnection()
                    .setRemoteDescription(
                            new CustomSdpObserver("publishVideo_setRemoteDescription"),
                            remoteSdpAnswer);
            if (mRtcRoomDelegateCallback!=null){
                mRtcRoomDelegateCallback.onReady();
            }
        });
        sendCmd(cmd);
    }


    private void onResopnse(String text) throws JSONException {
        JSONObject json = new JSONObject(text);
        if (json.has("result")) {
            final int rpcId = json.getInt("id");
            String result = json.getString("result");
            handleServerResponse(rpcId, result);
        } else if (json.has("error")) {
            handleServerError(json);
        } else {
//            handleServerEvent(json);
        }
    }

    private void handleServerResponse(int rpcId, String result) {
        AbsRequestCmd<?> cmd = mCmds.get(rpcId);
        mCmds.remove(rpcId);
        if (cmd != null) {
            Log.e(TAG,"cmd run:"+cmd.getMethodTag());
            cmd.onResponse(result);
        }
    }

    private void handleServerError(JSONObject json) throws JSONException {
        final JSONObject error = new JSONObject(json.getString("error"));
        final int errorCode = error.getInt("code");
        final String errorMessage = error.getString("message");
        Log.e(TAG, "Server error code " + errorCode + ": " + errorMessage);
    }

    //-------------------------------------------------------

    public void sendCmd(AbsRequestCmd<?> cmd) {
        int id = generateRPCId();
        mCmds.put(id, cmd);
        cmd.request(id, mSocket);
    }

    public void run(RtcRoomDelegateCallback callback) {
        mRtcRoomDelegateCallback=callback;
        mSocket.connect(mSession.getToken());
    }

    public void stop() {
        CmdLeaveRoom cmd = new CmdLeaveRoom();
        cmd.setCallback(resp -> {
            mSocket.disConnect();
        });
        sendCmd(cmd);
        mLocalParticipant.dispose();
        mSocket.disConnect();
        mSession.leaveSession();
    }

    public void playFile(String path) {
        ((LocalFileParticipan)mLocalParticipant).play(path);
    }
}
